#include "Assembler.h"

#include <iostream>
#include <fstream>

////////////////////////////////////////////
// Assembler class implementation begins

Assembler::Assembler (char* infname, char* outfname)
{
	int main, nlines;
	ipe = new IPE (&asm_instrs);
	nlines = ipe->ParseAssembly (infname, main);

	ofstream ofile (outfname);
	cout << "total = " << nlines << endl;
	ofile << nlines << " " << main << endl;
	for (int i = 0; i < nlines; i++)
	{
		cout << asm_instrs[i] << endl;
		bin_instrs.clear ();
		GenerateInstructions (asm_instrs[i]);
		ofile << bin_instrs.size ();
		cout << bin_instrs.size ();
		for (int j = 0; j < bin_instrs.size(); j++)
		{
			ofile << " " << bin_instrs[j];
			cout << " " << bin_instrs[j];
		}
		ofile << endl; cout << endl;
	}
	ofile.close ();
}

int Assembler::GenerateInstructions (string str)
{
	string instr, param;
	int memaddr, value;
	char buf[1024];

	ipe->ParseInstruction(str, instr, param);

	if (instr == "add" || instr == "and" || instr == "or" 
		|| instr == "mul" || instr == "div") {
		/*
		 * add instruction needs following operations:
		 *   select a memory location
		 *   read the data in the selected memory
		 *   move the value from MDR to register A
		 *   move the value from ACC to register B
		 *   perform ALU add/and/or/not operation
		 */
		INSTR inSelMem = 0, inReadMem = 0, inMoveMDRB = 0, 
		      	inMoveAccA = 0, inALUOp = 0;

		memaddr = ParseForMemAddress(param, value); 
		inSelMem |= WRITE_READ;
		inSelMem |= MAR_OUT;
		if (memaddr == -1) {
			inSelMem |= MAR_IMMED;
			sprintf (buf, "%u", inSelMem);
		} else 
			sprintf (buf, "%u %u", inSelMem, memaddr);
		bin_instrs.push_back (buf);

		inReadMem |= DATA_ADDR;
		inReadMem |= MDR_LATCH;
		if (memaddr == -1)
		{
			inReadMem |= MAR_IMMED;
			sprintf (buf, "%u %u", inReadMem, value);
		}
		else
		{
			sprintf (buf, "%u %u", inReadMem, memaddr);
		}
		bin_instrs.push_back (buf);

		inMoveMDRB |= MDR_OUT;
		inMoveMDRB |= B_LATCH;
		sprintf (buf, "%u", inMoveMDRB);
		bin_instrs.push_back (buf);

		inMoveAccA |= ACC_OUT;
		inMoveAccA |= A_LATCH;
		sprintf (buf, "%u", inMoveAccA);
		bin_instrs.push_back (buf);
		
		if (instr == "add") inALUOp |= ALU_ADD;
		else if (instr == "and") inALUOp |= ALU_AND;
		else if (instr == "or") inALUOp |= ALU_OR;
		else if (instr == "mul") inALUOp |= ALU_MUL;
		else if (instr == "div") {
			inALUOp |= ALU_MUL; 
			inALUOp |= MAR_IMMED;
		}
		inALUOp |= ACC_LATCH;
		sprintf (buf, "%u", inALUOp);
		bin_instrs.push_back (buf);

		return 5;		
	} else if (instr == "nota") {
		INSTR inNotAcc = 0;

		inNotAcc |= ACC_LATCH;
		inNotAcc |= ALU_NOT;
		sprintf (buf,"%u", inNotAcc);

		bin_instrs.push_back (buf);

		return 1;
	} else if (instr == "sub") {
		/*
		 * sub operation is not supported by ALU, it is implemented
		 * using add operation. This uses two's complement method. The
		 * operations involved are:
		 *   select memory location
		 *   read from memory location
		 *   move ACC to TEMP
		 *   move MDR to ACC
		 *   perform ALU NOT operation
		 *   move ACC to A
		 *   move ONE to B
		 *   perform ALU ADD operation
		 *   move ACC to B
		 *   move TEMP to A
		 *   perform ALU ADD operation   
		 */
		INSTR inSelMem = 0, inReadMem = 0, inMoveAccTemp = 0, 
			   inMoveMDRAcc = 0, inNot = 0, inMoveAccA = 0, 
			   inMoveOneB = 0, inIncr = 0, inMoveAccB = 0,
			   inMoveTempA = 0, inAdd = 0;

		memaddr = ParseForMemAddress (param, value);
		inSelMem |= WRITE_READ;
		inSelMem |= MAR_OUT;
		if(memaddr == -1) {
			inSelMem |= MAR_IMMED;
			sprintf (buf, "%u", inSelMem);
		} else {
			sprintf (buf, "%u %u", inSelMem, memaddr);
		}

		asm_instrs.push_back (buf);

		inReadMem |= DATA_ADDR;
		inReadMem |= MDR_LATCH;
		if (memaddr == -1)
		{
			inReadMem |= MAR_IMMED;
			sprintf (buf, "%u %u", inReadMem, value);
		}
		else
		{
			sprintf (buf, "%u %u", inReadMem, memaddr);
		}
		bin_instrs.push_back (buf);
		
		inMoveAccTemp |= ACC_OUT;
		inMoveAccTemp |= TEMP_LATCH;
		sprintf (buf, "%u", inMoveAccTemp);
		bin_instrs.push_back (buf);

		inMoveMDRAcc |= MDR_OUT;
		inMoveMDRAcc |= ACC_LATCH;
		sprintf (buf, "%u", inMoveMDRAcc);
		bin_instrs.push_back (buf);

		inNot |= ALU_NOT;
		inNot |= ACC_LATCH;
		sprintf (buf, "%u", inNot);
		bin_instrs.push_back (buf);

		inMoveAccA |= ACC_OUT;
		inMoveAccA |= A_LATCH;
		sprintf (buf, "%u", inMoveAccA);
		bin_instrs.push_back (buf);

		inMoveOneB |= ONE_OUT;
		inMoveOneB |= B_LATCH;
		sprintf (buf, "%u", inMoveOneB);
		bin_instrs.push_back (buf);

		inIncr |= ALU_ADD;
		inIncr |= ACC_LATCH;
		sprintf (buf, "%u", inIncr);
		bin_instrs.push_back (buf);

		inMoveAccB |= ACC_OUT;
		inMoveAccB |= B_LATCH;
		sprintf (buf, "%u", inMoveAccB);
		bin_instrs.push_back (buf);

		inMoveTempA |= TEMP_OUT;
		inMoveTempA |= A_LATCH;
		sprintf (buf, "%u", inMoveTempA);
		bin_instrs.push_back (buf);

		inAdd |= ALU_ADD;
		inAdd |= ACC_LATCH;
		sprintf (buf, "%u", inAdd);
		bin_instrs.push_back (buf);

		return 11;
	} else if (instr == "lda") {
		/*
		 * lda instruction needs following operations
		 *   select memory location
		 *   read from memory location
		 *   move from MDR to ACC register
		 */
		INSTR inSelMem = 0, inReadMem = 0, inMoveMDRAcc = 0;

		inSelMem |= WRITE_READ;
		inSelMem |= MAR_OUT;
		memaddr = ParseForMemAddress (param, value);
		if (memaddr == -1)
		{
			inSelMem |= MAR_IMMED;
			sprintf (buf, "%u", inSelMem);
		}
		else
		{
			sprintf (buf, "%u %u", inSelMem, memaddr);
		}
		bin_instrs.push_back (buf);
		
		inReadMem |= DATA_ADDR;
		inReadMem |= MDR_LATCH;
		if (memaddr == -1)
		{
			inReadMem |= MAR_IMMED;
			sprintf (buf, "%u %u", inReadMem, value);
		}
		else
		{
			sprintf (buf, "%u %u", inReadMem, memaddr);
		}
		bin_instrs.push_back (buf);

		inMoveMDRAcc |= MDR_OUT;
		inMoveMDRAcc |= ACC_LATCH;
		sprintf (buf, "%u", inMoveMDRAcc);
		bin_instrs.push_back (buf);

		return 3;
	} else if (instr == "sta") {
		/*
		 * sta instruction needs following operations
		 *   move from ACC to MDR
		 *   select memory location
		 *   write to memory location
		 */
		INSTR inMoveAccMDR = 0, inSelMem = 0, inWriteMem = 0;

		memaddr = ParseForMemAddress (param, value);	
		inSelMem |= MAR_OUT;
		inSelMem |= WRITE_READ;
		if (memaddr == -1) 
		{
			inSelMem |= MAR_IMMED;
			sprintf (buf, "%u", inSelMem);
		}
		else
		{
			sprintf (buf, "%u %u", inSelMem, memaddr);
		}
		bin_instrs.push_back (buf);

		if (memaddr == -1) {
			// if in immed mode, value is to be stored
			// in acc

			inWriteMem |= DATA_ADDR;
			inWriteMem |= MDR_LATCH;
			inWriteMem |= MAR_IMMED;
			sprintf (buf, "%u", inWriteMem, value);
			bin_instrs.push_back (buf);
			
			inMoveAccMDR |= ACC_LATCH;
			inMoveAccMDR |= MDR_OUT;
			sprintf (buf, "%u", inMoveAccMDR);
			bin_instrs.push_back (buf);

			return 3;
		} else {
			inMoveAccMDR |= ACC_OUT;
			inMoveAccMDR |= MDR_LATCH;
			sprintf (buf, "%u", inMoveAccMDR);
			bin_instrs.push_back (buf);

			inWriteMem |= DATA_ADDR;
			inWriteMem |= WRITE_READ;
			inWriteMem |= MDR_OUT;
			sprintf (buf, "%u %u", inWriteMem, memaddr);
			bin_instrs.push_back (buf);

			return 3;
		}
	} else if (instr == "bl" || instr == "bg" || instr == "ble"
			|| instr == "bge" || instr == "ba" || instr == "be" ||
			instr == "jmp") {
		// If it is a jump instruction, perform jump operation
		INSTR inB = 0;

		memaddr = atoi (param.c_str());
		
		if (instr == "bl")
			inB |= BCOND_BL;
		else if (instr == "bg")
			inB |= BCOND_BG;
		else if (instr == "ble")
			inB |= BCOND_BLE;
		else if (instr == "bge")
			inB |= BCOND_BGE;
		else if (instr == "ba")
			inB |= BCOND_BA;
		else if (instr == "be")
			inB |= BCOND_BE;
		else if (instr == "bne") {
			inB |= BCOND_BE;
			inB |= MAR_IMMED;
		}
		else if (instr == "jmp")
			inB |= BCOND_JMP;

		sprintf (buf, "%u %u", inB, memaddr);
		bin_instrs.push_back (buf);

		return 1;
	} else if (instr == "ret") {
		sprintf (buf, "%u", BCOND_RET);
		bin_instrs.push_back (buf);
		return 1;
	} else if (instr == "nop") {
		bin_instrs.push_back ("0");
		return 1;
	} else {
		fprintf (stderr,"%s\n", instr.c_str());
		errprint ("Invalid instruction");
	}
}

/*
 * Parses for mem address from the string. Most instructions, which take
 * param accept two forms of addressing. Direct addressing or immediate
 * mode. This function parses the param, and if it finds an immediate mode
 * number, then the value is stored at mem location -1 and address -1 is
 * returned.
 */
int Assembler::ParseForMemAddress (string str, int& value)
{
	const char *cstr = str.c_str ();
	int addr;
	if (str[0] == '$') {
		// This is direct addressing
		if (sscanf (cstr,"$%d", &addr) == 0) {
			errprint ("Invalid Memory Address");
		}
		if (addr < 0 && addr > 255) {
			errprint ("This memory address results in segfault");
		}
		return addr;
	} else if (str[0] == '#') {
		// This is an immediate mode value
		if(sscanf (cstr,"#$%d", &addr) == 0) {
			errprint ("Invalid Memory Address");
		}
		value = addr;
		return -1;
	} else {
		if (sscanf (cstr, "%d", &addr) == 0) {
			errprint ("Invalid Memory Address");
		}
		value = addr;
		return -1;
	}
}

void Assembler::errprint (const char *msg)
{
	string mstr = msg;
//	mstr = mstr + " " + currentInstr;
	//errprintline (mstr.c_str(), IR);
	cout << mstr;// << " " << IR;
}

// Assembler class implementation ends
////////////////////////////////////////////

int main (int argc, char* argv[])
{
	if (argc < 3)
	{
		cerr << "USAGE: " << argv[0] << " <inputfile> <outputfile>" 
			<< endl;
		return 1;
	}

	Assembler as (argv[1], argv[2]);
	
	return 0;
}
